//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using OpenLS.Core.Serialization;
using OpenLS.Spreadsheet.Internals;

namespace OpenLS.Spreadsheet
{
	class RowReader
	{
		private readonly Queue<Row.RowSerializationData> _data = new Queue<Row.RowSerializationData>();
		private bool _isTerminated;

		public void Add (Row.RowSerializationData item)
		{
			lock (_data)
			{
				_data.Enqueue(item);
			}
		}
		public void Terminate()
		{
			_isTerminated = true;
		}
		public bool IsTerminated { get { return _isTerminated; } }
		public Row.RowSerializationData GetItem()
		{
			lock (_data)
			{
				if (_data.Count > 0)
					return _data.Dequeue();
				return null;
			}
		}
	}
	//\\using RowCollection = DimensionCollection<Row>;
	
	public partial class Row: Dimension
	{
		internal Row(Worksheet ws): base (ws)
		{
		}
		internal Cell this[int i]
		{
			get
			{
				return Worksheet[Index, i];
			}
		}
		internal override double Offset
		{
			get { return Worksheet.Rows.GetOffset(Index); }
		}

#if DEBUG2
		[ContractInvariantMethod]
		internal override void ObjectInvariant()
		{
			Debug.Assert(Worksheet.Rows[Index] == this);
			base.ObjectInvariant();
			foreach (int key in _cells.Keys)
			{
				Cell c = _cells[key];
				Debug.Assert(c != null);
				if (c.RowSpan == 1 && c.ColumnSpan == 1)
				{
					Debug.Assert(c.Row == this);
					Debug.Assert(c.Column.Index == key);
				}
			}
		}
#endif

		internal override int MaxValidOffsetIndex
		{
			get
			{
				return Worksheet.Rows.MaxValidOffsetIndex;
			}
			set
			{
				Worksheet.Rows.MaxValidOffsetIndex = value;
			}
		}
		public static Row operator +(Row This, int offset)
		{
			return This.Worksheet.Rows[This.Index + offset];
		}
		internal override Dimension GetOffseted(CellRelativeAddress address)
		{
			return this.Worksheet.Rows [this.Index + address.Row];
		}

		internal const int MaxIndex = 1048575;
		internal const int MaxSpan = MaxIndex + 1;

		public override double Length
		{
			get { return ActualHeight; }
		}

		internal override bool IsEmpty
		{
			get { return Count == 0 && _height == null; }
		}

		internal override string UnsafeLabel
		{
			get
			{
				return (Index + 1).ToString(); 
			}
		}
		



		private double? _height;

		/// <summary>
		/// Get or set the declared height of the row. If the value is null, the actual height will be the default row height of the worksheet.
		/// </summary>
		public double? Height
		{
			get { return _height; }
			set
			{
				if (value == _height)
				{
					if (value == null && _actualHeight != null)
					{
						_actualHeight = null;
						NotifyPropertyChanged("ActualHeight");
					}
					return;
				}
				RowHeightUndoUnitContainer c = null;
				if (Worksheet.Workbook.UndoEnabled)
				{
					c = new RowHeightUndoUnitContainer(this);
					c.Add(new RowHeightUndoUnit(this));
				}
				using (Worksheet.CreateUndoScope(c))
				{
					CustomHeight = true;
					if (value != null)
					{
						double v = value.Value;
						if (v < 0)
							value = 0;
					} 
					_height = value;
					_actualHeight = null;
					Worksheet.Rows.TruncateOffsets(Index, ActualHeight);
					NotifyPropertyChanged("Height");
					NotifyPropertyChanged("ActualHeight");
					Worksheet.Rows.RaiseLayoutChanged(this);
				}
			}
		}

		///<summary>
		/// Set the height of the row so that it fits its content
		///</summary>
		public void AutoFit()
		{
			using (var c = this.Workbook.CreateUndoScope(StringTable.Anchor))
			{
				Height = null;
				CustomHeight = false;
				_actualHeight = null;
				Worksheet.Rows.TruncateOffsets(Index, ActualHeight);
				NotifyPropertyChanged("Height");
				NotifyPropertyChanged("ActualHeight");
				Worksheet.Rows.RaiseLayoutChanged(this);
			}

		}

		double? _actualHeight;
		public double ActualHeight
		{
			get {
				if (!IsVisible)
					return 0;
				if (_height != null)
					return _height.Value;
				if (_actualHeight == null)
				{
					_actualHeight = Enumerable.Aggregate(GetNonEmptyCells(),Worksheet.DefaultRowHeight,
														(dd, c) => Math.Max(dd, c.DesiredHeight));
					Worksheet.Rows.TruncateOffsets(Index, ActualHeight);
				}
				return _actualHeight.Value;
			}
		}


		internal void ReadXml(ReadContext context, int previousIndex, ReadContext c, StyleRecordManager styleRecordManager)
		{
			c.PassAttributes();
			int previousColumnIndex = -1;
			while (c.ReaderLocalName == "c")
			{
				Cell cell = Cell.CreateCellWithRow(this);
				cell.ReadXml(c, previousColumnIndex, styleRecordManager);
				previousColumnIndex = cell.Column.Index;
				AddCellToDictionary(cell.Column.Index, cell);
				Contract.Assert(cell.Row == this);

			}
			c.Dispose();
		}

		internal static RowSerializationData ReadXmlData(int previousIndex, ReadContext c, StyleRecordManager styleRecordManager)
		{
			c.PassAttributes();
			RowSerializationData result = new RowSerializationData();
			int previousColumnIndex = -1;
			while (c.ReaderLocalName == "c")
			{
				result.Add(Cell.ReadXmlData(c, previousColumnIndex, styleRecordManager));

			}
			c.Dispose();
			return result;
		}
		internal class RowSerializationData
		{
			public RowAttributes attributes;
			private List<Cell.CellSerializationData> cells = new List<Cell.CellSerializationData>();

			public void Add(Cell.CellSerializationData readXmlData)
			{
				this.cells.Add(readXmlData);
			}

			public void Apply(Worksheet worksheet, GroupBuilderStack<Row> stack, StyleRecordManager styleRecordManager, WorkbookResolver workbookResolver)
			{
				Row row = worksheet.Rows[attributes.Index];
				attributes.Apply(row, stack);
				int columnIndex = -1;
				foreach (var cell in this.cells)
				{
					columnIndex = cell.Apply(row, columnIndex, styleRecordManager, workbookResolver);
					Contract.Assert(columnIndex <= Worksheet.MaxColumnIndex);
				}
				foreach (Cell cell in row.GetNonEmptyCells())
				{
					worksheet.NonVolatileCells.AddNoRow(cell);
					cell.Column.Add(cell);
				}
				Contract.Assert(Enumerable.Count(row.GetNonEmptyCells())>= this.cells.Count);
			}
		}

		internal void Add(Cell cell)
		{
			Contract.Requires(cell != null);
			if (cell.ColumnSpan == 1)
				AddCellToDictionary(cell.Column.Index, cell);
			else
				foreach (Column col in cell.GetColumns())
					AddCellToDictionary(col.Index, cell);
		}
		internal void Remove(Cell cell)
		{
			Contract.Requires(cell != null);
			foreach (Column col in cell.GetColumns())
				RemoveCellFromDictionary(col.Index);
		}
		internal IEnumerable<CellAddress> GetCellAddresses()
		{
			var columns = new List<int>();
			foreach (int column in CellKeys)
			{
				columns.Add(column);
			}
			columns.Sort();
			foreach (int column in columns)
				yield return new CellAddress(Index, column);
		}
		internal bool IsCustom
		{
			get
			{
				return _height != null || _hidden || FormattingRecord != null || IsCollapsed || thickBot || thickTop || OutlineLevel > 0;

			}
		}
		internal void WriteXml(WriteContext context)
		{
			;
			//foreach (Cell c in _cells.Values)
			//  if (!c.IsEmpty)
			//    return true;
			//return false;
			if (!Enumerable.Any(GetCells(), c => (!c.IsEmpty && c.Row == this)) && !IsCustom)
				return;
			using (WriteContext c = context.Write("row"))
			{
				c.SetString("r", Label);
				int min = int.MaxValue;
				int max = int.MinValue;
				bool hasCell = false;
				foreach (Cell cell in GetNonEmptyCells())
				{
					min = Math.Min(min, cell.Column.Index);
					max = Math.Max(max, cell.Column.Index + cell.ColumnSpan - 1);
					hasCell = true;
				}
				min++;
				max++;
				if (hasCell)
					c.SetString("spans", min + ":" + max);// to implement;                if (height != null)
				if (FormattingRecord != null)
				{
					c.SetInteger("s", FormattingRecord.Id);
					c.SetBoolean("customFormat", true);
				}
				if (_height != null)
					c.SetDouble("ht", PointsToPixelConverter.ToPoint(ActualHeight));
				c.SetOptionalBoolean("customHeight", CustomHeight || _height != null, false);
				c.SetOptionalBoolean("hidden", IsHidden, false);
				c.SetOptionalInteger("outlineLevel", OutlineLevel, 0);
				c.SetOptionalBoolean("collapsed", IsCollapsed, false);
				c.SetOptionalBoolean("thickBot", thickBot, false);
				c.SetOptionalBoolean("thickTop", thickTop, false);
				c.SetOptionalBoolean("ph", _ph, false);

				foreach (CellAddress a in GetCellAddresses())
					CellAt(a.Column).WriteXml(c, a);
			}
			
		}

		private bool _ph;

		public bool ShowPhonetic
		{
			get { return _ph; }
			set { _ph = value;NotifyPropertyChanged("ShowPhonetic"); }
		}
	

		private bool thickTop;

		public bool ThickTop
		{
			get { return thickTop; }
			set { thickTop = value; }
		}
		private bool thickBot;

		public bool ThickBottom
		{
			get { return thickBot; }
			set { thickBot = value; }
		}


		//  private byte outlineLevel;


		private bool _hidden;

		internal override bool IsHidden
		{
			get { return _hidden; }
			set
			{
				if (_hidden == value)
				{
					return;
				}
				_hidden = value;
				//\\NotifyPropertyChanged("IsHidden");
				Worksheet.Rows.TruncateOffsets(Index, ActualHeight);
				NotifyPropertyChanged("IsVisible");
				NotifyPropertyChanged("ActualHeight");
				Worksheet.Rows.RaiseLayoutChanged(this);
			}
		}
	



		internal void RemoveAt(int index)
		{
			RemoveCellFromDictionary(index);
		}


		internal override bool GetIsSelected()
		{
			if (Worksheet.Selection == null)
				return false;
			return  Range.Intersects(Worksheet.Selection, new FullRowRange (this));
		}

		#region IWithAddress Members

		public CellAddress Address
		{
			get {
				return new CellAddress(Index, 0);
			}
		}

		#endregion

		internal void InvalidateHeight()
		{
			if (_height == null)
			{
				_actualHeight = null;
				Worksheet.Rows.TruncateOffsets(Index, ActualHeight);
				NotifyPropertyChanged("Height");
				NotifyPropertyChanged("ActualHeight");
				Worksheet.Rows.RaiseLayoutChanged(this);
			}
		}


		internal override void OnIsVisibleChanged()
		{
			Worksheet.Rows.TruncateOffsets(Index, ActualHeight);
			NotifyPropertyChanged("Height");
			NotifyPropertyChanged("ActualHeight");
			Worksheet.Rows.RaiseLayoutChanged(this);

		}

		internal override string LengthSrid
		{
			get { return StringTable.RowHeight; }
		}

		internal override Dimension GetPrevious()
		{
			return Worksheet.Rows[Index - 1];
		}

		private bool _customHeight;
		internal bool CustomHeight
		{
			get { return _customHeight; }
			set
			{
				if (_customHeight == value)
					return;
				this.Workbook.AddUndoAction(value, v => this.CustomHeight = v);
				_customHeight = value;
			}
		}

		internal double BottomOffset
		{
			get { return Offset + ActualHeight; }
		}

		internal static bool ValidIndex(int row1)
		{
			return row1 >= 0 && row1 <= MaxIndex;
		}

	}
}
